package root.item;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentSkipListSet;

import root.historic.event.InteressePeloItemEvent;
import root.historic.event.ItemEvent;
import root.user.User;
import strategy.GerenciadorItemIF;
import exceptions.AbsentAtributException;
import exceptions.HasAlreadyDoneException;
import exceptions.InexistentCriterySortType;
import exceptions.InexistentSortType;
import exceptions.InvalidAtributException;
import exceptions.InvalidCriterySortType;
import exceptions.InvalidItemCategoryException;
import exceptions.InvalidItemNameException;
import exceptions.InvalidKeyException;
import exceptions.InvalidMessage;
import exceptions.InvalidReceiver;
import exceptions.InvalidSender;
import exceptions.InvalidSortType;
import exceptions.InvalidSubject;
import exceptions.IsNotOnwerException;
import exceptions.ItemDoesNotExist;
import exceptions.PermissionDeniedException;

/**
 * Classe gerenciadora de itens.
 */
public class GerenciadorItem implements GerenciadorItemIF{

	private Collection<Item> itens;
	private Map<String, Item> map;
	private static long nextId = 0;
	private Collection<ItemListener> listeners;;
	private Map<String, PublicarPedido> mapPublicacoesDePedido;
	public GerenciadorItem(){
		itens = new ConcurrentSkipListSet<Item>();
		listeners = new ArrayList<ItemListener>();
		map = new TreeMap<String, Item >();
		mapPublicacoesDePedido = new TreeMap<String, PublicarPedido>();
	}
	
	public Item getItem(String id) throws ItemDoesNotExist {
		if(id == null || id.equals("")){
			throw new ItemDoesNotExist("Identificador do item é inválido");
		}
		if (!hasItem(id)) {
			throw new ItemDoesNotExist("Item inexistente");
		}
		return map.get(id);
	}
		
	public boolean hasItem(String id) {
		if (id == null) {
			return false;
		}
		return map.containsKey(id);
	}
	
	public void addListener(ItemListener listener){
		listeners.add(listener);
	}
	
	public void removeListener(ItemListener listener){
		listeners.remove(listener);
	}
	
	public boolean contains(ItemListener listener){
		return listeners.contains(listener);
	}
	
	private String getIdPublicarPedido(User dono, String nomeItem, String descricaoItem){
		String resp = "";
		resp=resp.concat(dono.getUsername());
		resp=resp.concat(nomeItem);
		resp=resp.concat(descricaoItem);
		return resp;
	}
	
	public PublicarPedido getPedidoItem(String pedidoId) throws InvalidAtributException{
		if(pedidoId==null || pedidoId.equals("")){
			throw new InvalidAtributException("Identificador da publicação de pedido é inválido");
		}
		if(!mapPublicacoesDePedido.containsKey(pedidoId)){
			throw new InvalidAtributException("Publicação de pedido inexistente");
		}
		return mapPublicacoesDePedido.get(pedidoId);
	}
	
	public String addPublicarPedido(User dono, String nomeItem, String descricaoItem) throws InvalidAtributException{
		if(nomeItem==null||nomeItem.isEmpty()){
			throw new InvalidAtributException("Nome inválido");
		}
		if(descricaoItem==null || descricaoItem.isEmpty()){
			throw new InvalidAtributException("Descrição inválida");
		}
		String id = getIdPublicarPedido(dono, nomeItem, descricaoItem);
		mapPublicacoesDePedido.put(id, new PublicarPedido(dono, nomeItem, descricaoItem));
		for(ItemListener listener : listeners){
			listener.notifyPublicarPedido(new PublicarPedidoEvent(dono, nomeItem, descricaoItem));
		}
		
		return id;
		
	}
	
	public void rePublicarPedido(PublicarPedido pedido) throws InvalidAtributException{
		if(pedido==null){
			throw new InvalidAtributException("Identificador da publicação de pedido é inválido");
		}
		String nomeItem = pedido.getNomeItem();
		String descricaoDoItem = pedido.getDescricaoDoItem();
		for(ItemListener listener : listeners){
			listener.notifyPublicarPedido(new PublicarPedidoEvent(pedido.getDonoPedido(), nomeItem, descricaoDoItem));
		}
		
	}
	
	public void oferecerItem(User dono, PublicarPedido pedido, String itemId) throws InvalidAtributException, ItemDoesNotExist, InvalidSender, InvalidReceiver, InvalidMessage, InvalidSubject{
		if(dono == null || pedido == null){
			throw new InvalidAtributException();
		}
		Item item = getItem(itemId);
		ofereceuItem(new ItemOferecidoEvent(dono, item, pedido.getDonoPedido()));
		
	}
	private void ofereceuItem(ItemOferecidoEvent itemOferecidoEvent) {
		for(ItemListener listaner: listeners){
			listaner.notifyOfereceuItem(itemOferecidoEvent);
		}
		
	}

	public String addItem(User owner, String name , String description, String category) throws InvalidItemNameException, InvalidItemCategoryException{
		String id = getNewAvaibleID();
		if(category == null || category.equals("")){
			throw new InvalidItemCategoryException("Categoria inválida");
		}
		Item item = new Item(name, description, category, id, owner);
		
		itens.add( item );
		for(ItemListener listener : listeners){
			listener.notifyItemCadastrado(new ItemEvent(this,owner, item));
		}
		map.put(id, item);
		return id;
	}
	
	public void deleteItem(User user, Item item) throws ItemDoesNotExist, IsNotOnwerException, PermissionDeniedException{
		if(!user.equals(item.getOwner())){
			throw new IsNotOnwerException("O usuário não tem permissão para apagar este item");
		}
		if(item.isBorrowed()){
			throw new PermissionDeniedException("O usuário não pode apagar este item enquanto estiver emprestado");
		}
		itens.remove(item);
		map.remove(item.getId());
	}
	
	private String getNewAvaibleID() {
		String returnValue = "";
		//do{
			//returnValue = Long.toBinaryString(rand.nextLong()) + Long.toBinaryString(rand.nextLong());
			returnValue = String.valueOf(++nextId);
		//} while (hasItem(returnValue));
		return returnValue;
	}
	
	public Collection<Item> getItems() {
		return  itens;
	}
	
	public String getItensName(Collection<Item> items, String message) {
		String resp="";
		
		if(items.size() == 0){
			resp += message;
		} else {
			Iterator<Item> it = items.iterator();
			while( it.hasNext() ){
				String next = it.next().getName();
				resp=resp.concat(next);
				if(it.hasNext()) resp =resp.concat( "; ");
			}
		}
		return resp;
	}
	
	public String getItensName(Collection<Item> items) {
		return getItensName(items, "O usuário não possui itens cadastrados");
	}
	
	private ItemComparator getComparatorItem(String modo) throws InexistentCriterySortType, InvalidCriterySortType {
		if (modo == null || modo.isEmpty()) {
			throw new InvalidCriterySortType("Critério inválido de ordenação");
		}
		if (modo.equals("dataCriacao")) {
			return new ItemComparatorDate();
		}
		else if (modo.equals("reputacao")) {
			return new ItemComparatorRankDono();
		}
		else {
			throw new InexistentCriterySortType("Critério de ordenação inexistente");
			
		}
	}
	
	public void verifyKey(String key) throws InvalidKeyException {
		if (key == null || key.isEmpty()) {
			throw new InvalidKeyException("Chave inválida");
		}
	}
	
	/**
	 * Verifica se o tipo de ordenacao eh valido
	 * -Crescente
	 * -Decrecente
	 * @param ordType
	 * @return
	 * @throws InvalidSortType
	 * @throws InexistentSortType
	 */
	public boolean getSortType(String ordType) throws InvalidSortType, InexistentSortType {
		
		if (ordType == null || ordType.isEmpty()) {
			throw new InvalidSortType("Tipo inválido de ordenação");
		}
		if (ordType.equals("crescente")) {
			return true;
		}
		else if (ordType.equals("decrescente")) {
			return false;
		}
		throw new InexistentSortType("Tipo de ordenação inexistente");
		
	}
	
	
	/**
	 * Retorna uma lista de itens ordenada pelo criterio definido.
	 * Utiliza os itens dos seus amigos.
	 * @param user
	 * 	Qm pediu a lista
	 * @param key
	 * 	Chave quex ele quer que seja procurada
	 * @param attri
	 * 	Attributo
	 * @param ordType
	 * 	Tipo da ordenacao
	 * @param critery
	 * 	Criterio a ser seguido
	 * @return
	 * @throws InvalidKeyException 
	 * @throws InexistentSortType 
	 * @throws InvalidSortType 
	 * @throws InvalidCriterySortType 
	 * @throws InexistentCriterySortType 
	 * @throws ItemDoesNotExist 
	 * @throws AbsentAtributException 
	 * @throws InvalidAtributException 
	 */
	private Collection<Item> getSortedItems(Collection<Item> items, String key, String attri, String ordType, String critery) throws InvalidKeyException, InvalidSortType, InexistentSortType, InexistentCriterySortType, InvalidCriterySortType, InvalidAtributException, AbsentAtributException, ItemDoesNotExist {
		verifyKey(key);
		List<Item> list = filterItems(items, key, attri);
		boolean isAscending = getSortType(ordType);
		Collections.sort(list, getComparatorItem(critery));
		if (!isAscending) {
			Collections.reverse(list);
		}
		return list;
	}
	/**
	 * Retorna uma string contendo os itens dos seus amigos
	 * que satisfazem a propriedade
	 * Key esta contido no attributo do item.
	 * Essa mesma lista esta ordenada por ordType seguindo o criterio criteyr.
	 * @param user
	 * @param key
	 * @param attri
	 * @param ordType
	 * @param critery
	 * @return
	 * @throws InvalidKeyException
	 * @throws InvalidSortType
	 * @throws InexistentSortType
	 * @throws InexistentCriterySortType
	 * @throws InvalidCriterySortType
	 * @throws InvalidAtributException
	 * @throws AbsentAtributException
	 * @throws ItemDoesNotExist
	 */
	public String searchItems(Collection<Item> items, String key, String attri, String ordType, String critery) throws InvalidKeyException, InvalidSortType, InexistentSortType, InexistentCriterySortType, InvalidCriterySortType, InvalidAtributException, AbsentAtributException, ItemDoesNotExist {
		return getItensName( getSortedItems(items, key, attri, ordType, critery) , "Nenhum item encontrado");
		
	}
	
	/**
	 * Retorna todos os itens
	 * que sigam determinado comportamento, ou seja
	 * Que key esteja contido em attributo
	 * @param user
	 * @param key
	 * @param attri
	 * @return
	 * @throws ItemDoesNotExist 
	 * @throws AbsentAtributException 
	 * @throws InvalidAtributException 
	 * @throws InvalidKeyException 
	 */
	private List<Item> filterItems(Collection<Item> items, String key, String attri) throws InvalidAtributException, AbsentAtributException, ItemDoesNotExist, InvalidKeyException {
		verifyKey(key);
		List<Item> retList = new ArrayList<Item>();
		for (Item itemLooking : items) {
			String completeString = getAtributoItem(itemLooking, attri);
			
			completeString = completeString.toLowerCase();
			key = key.toLowerCase();
			if (completeString.contains(key)) {
				retList.add(itemLooking);
			}
		}
		
		return retList;
	}
	
	/**
	 * O usuario {@code user} registra interesse no item de identidicacao {@code id}, dessa forma o usuario recebe uma mensagem
	 * offtopic quando o item esta ja esta a disposicao para emprestimo.<br>
	 * Essa mensagem é enviada por ação do evento de confirmação de termino de emprestimo pelo dono do item.
	 * 
	 * @param user
	 * @param gerenciadorItemDoUser
	 * @param item
	 * @throws ItemDoesNotExist
	 * @throws InvalidAtributException
	 * @throws PermissionDeniedException
	 * @throws HasAlreadyDoneException
	 */
	public void addItemInterest(User user, GerenciadorItemIF gerenciadorItemDoUser, Item item) throws ItemDoesNotExist, InvalidAtributException, PermissionDeniedException, HasAlreadyDoneException{
		if(user.equals(item.getOwner())){
			throw new InvalidAtributException("O usuário não pode registrar interesse no próprio item");
		}
		if(!user.isFriend(item.getOwner())){
			throw new PermissionDeniedException("O usuário não tem permissão para registrar interesse neste item");
		}
		if(gerenciadorItemDoUser.contains(new ItemInterest(user,item))){
			throw new PermissionDeniedException("O usuário já registrou interesse neste item");
		}
		gerenciadorItemDoUser.addListener(new ItemInterest(user,item));
		for(ItemListener listener: listeners){
			listener.notifyInteressePeloItem(new InteressePeloItemEvent(item.getOwner(), user, item));
		}
	}
	
	public void addItemInterest(User user,GerenciadorItemIF gerenciadorItemDoUser, String itemId) throws ItemDoesNotExist, InvalidAtributException, PermissionDeniedException, HasAlreadyDoneException{
		addItemInterest(user, gerenciadorItemDoUser, getItem(itemId));
	}
	
	public boolean hasItem(Item item) {
		if (item == null) {
			return false;
		}
		return itens.contains(item);
	}
	
	public String getAtributoItem(String itemId,String atributo) throws InvalidAtributException, AbsentAtributException, ItemDoesNotExist{
		return getAtributoItem(getItem(itemId), atributo);
	}
	
	public String getAtributoItem(Item item,String atributo) throws InvalidAtributException, AbsentAtributException, ItemDoesNotExist{
		String resp="";
		if(atributo==null||atributo.equals("")){
			throw new InvalidAtributException("Atributo inválido");
		}
		else if(atributo.equals("nome")){
			resp=item.getName();
		}
		else if(atributo.equals("descricao")){
			resp=item.getDescription();
			
		}
		else if(atributo.equals("categoria")){
			resp=item.getCategory();
		}
		else{
			throw new AbsentAtributException("Atributo inexistente");
		}
		return resp;
		
	}
	
	public User getOwner(Item item){
		return item.getOwner();
	}

	public synchronized void devolution(Item item){
		item.devolution();
		Iterator<ItemListener> it = listeners.iterator();
		while(it.hasNext()){
			ItemListener itemListener = it.next(); 
			itemListener.itemDevolvido(new ItemEvent(this, item.getOwner(), item));
		}
	}
	
	public void lend(Item item, User interested, int days){
		item.lend();
	}

}
